import os.path
import zipfile
import xmltodict
import base64

from PIL import Image


def get_tree(path_or_bytes):
    file = open(path_or_bytes, "r", encoding="utf-8")
    _text = file.read()
    tree = xmltodict.parse(_text)
    file.close()

    return tree


def unzip_file(zip_path, unzip_path=None):
    """
    :param zip_path: ofd格式文件路径
    :param unzip_path: 解压后的文件存放目录
    :return: unzip_path
    """
    if not unzip_path:
        unzip_path = zip_path.replace(".ofd", "")
    try:
        with zipfile.ZipFile(zip_path, "r") as f:
            for file in f.namelist():
                f.extract(file, path=unzip_path)
    except Exception as e:
        print("Can't unzip this file: " + zip_path)
    return unzip_path


def find_inner_dicts(data, key):
    """
    递归查找当前xml树中的线条绘图对象和文本对象
    :param data:
    :param key:
    :return:
    """
    inner_dicts = []  # 保存所有最深处的字典
    if isinstance(data, dict):  # 如果当前数据是字典
        if key in data.keys():
            if isinstance(data[key], dict):
                inner_dicts.append(data[key])
            else:
                inner_dicts.extend(data[key])
        # 将当前字典加入列表
        for value in data.values():
            inner_dicts.extend(
                find_inner_dicts(value, key)
            )  # 递归地查找所有最深处的字典
    elif isinstance(data, list):  # 如果当前数据是列表
        for item in data:
            inner_dicts.extend(
                find_inner_dicts(item, key)
            )  # 递归地查找所有最深处的字典
    return inner_dicts


def get_content_and_line(content_tree, is_template=False):
    content, line, title = [], [], []
    layer = content_tree["ofd:Page"]["ofd:Content"]["ofd:Layer"]

    content_dicts = find_inner_dicts(layer, key="ofd:TextObject")
    line_dicts = find_inner_dicts(layer, key="ofd:PathObject")

    for inner_dict in content_dicts:
        if "ofd:TextCode" in inner_dict.keys():
            if not (
                    "ofd:FillColor" in inner_dict.keys()
                    and inner_dict["ofd:FillColor"] is not None
                    and inner_dict["ofd:FillColor"]["@Value"] != "0 0 0"
            ):
                content.append(inner_dict)

    for inner_dict in line_dicts:
        # if ('ofd:TextCode' not in inner_dict.keys() and
        #       'ofd:StrokeColor' in inner_dict.keys() and
        #       not (inner_dict['ofd:StrokeColor'] == 'None' and
        #       inner_dict['ofd:StrokeColor']['@Value'] == '0 0 0')):
        line.append(inner_dict)

    # if isinstance(layer, list):
    #     for lyr in layer:
    #         if 'ofd:TextObject' in lyr.keys():
    #             layer = lyr
    # while 'ofd:TextObject' not in layer.keys():
    #     layer = layer['ofd:PageBlock']
    #
    # if not is_template:
    #     content = layer['ofd:TextObject']
    #
    # if 'ofd:PathObject' in layer.keys():
    #     line = layer['ofd:PathObject']

    return content, line


def process_content(content):
    """
    文本元素列表提取方法
    :param content: xml文件中提取出的元素tree
    :return: 文本元素列表
    """

    content_list = []
    for text_obj in content:
        if "#text" in text_obj["ofd:TextCode"].keys():
            # if 'ofd:TextCode' in text_obj.keys() and 'ofd:FillColor' not in text_obj.keys():  # 通过FillColor筛选掉标题文本
            bound = text_obj["@Boundary"].split()
            assert (
                    "@X" in text_obj["ofd:TextCode"].keys()
                    and "@Y" in text_obj["ofd:TextCode"].keys()
            )
            x_bias, y_bias = float(text_obj["ofd:TextCode"]["@X"]), float(
                text_obj["ofd:TextCode"]["@Y"]
            )
            x, y = int(float(bound[0]) + x_bias), int(float(bound[1]) + y_bias)

            text = text_obj["ofd:TextCode"]["#text"]
            element_id = text_obj["@ID"]

            content_list.append([element_id, x, y, text])

    # 先按照y进行排序,再按照x进行排序,遍历时模拟从上到下，从左到右的顺序
    content_list.sort(key=lambda element: (element[2], element[1]))

    return content_list


def get_limit(line):
    """
    获取绘制的横线和竖线的x或y坐标
    :param line: pathObject的对象列表
    :return:
        x_list: 竖线的x坐标
        y_list: 横线的y坐标
    """

    x_list, y_list = [], []
    for line_obj in line:
        bound = line_obj["@Boundary"].split()
        x, y, x_l, y_l = (
            int(float(bound[0])),
            int(float(bound[1])),
            int(float(bound[2])),
            int(float(bound[3])),
        )
        if x_l > y_l:
            y_list.append(y)
        else:
            x_list.append(x)

    # 排序以便后续通过倒数线条序号获取坐标
    x_list = sorted(set(x_list))
    y_list = sorted(set(y_list))
    return x_list, y_list


def get_span(content_list, y_list, idx):
    """
    获取每个行项目的起始y坐标和所有行项目元素的x坐标
    :param content_list: 文本元素列表
    :param y_list: 横向线条y坐标列表
    :param idx: 横向线条索引
    :return: 每个行项目的起始y坐标和所有行项目元素的x坐标
    """

    span_y, span_x = [], []
    for text in content_list:
        # pattern = r'\d+%'
        # matches = re.match(pattern, text[3])
        if "%" in text[3] and text[1] > 100:
            span_y.append(text[2])
        if "¥" in text[3] and text[2] < y_list[idx + 1]:
            span_y.append(text[2])
            break
    for text in content_list:
        if y_list[idx] < text[2] < span_y[-1]:
            span_x.append(text[1])
    span_y = sorted(set(span_y))
    span_x = sorted(set(span_x))

    # with open("./Data/span_x.txt", "a") as f:
    #     f.write(",".join([str(x) for x in span_x]))
    #     f.write("\n")

    return span_y, span_x


def group_data(content_list, span_y, y_line):
    # FIXME:完善合并逻辑
    data_dict = {}
    for text in content_list:
        key = text[2]
        if key not in data_dict.keys():
            data_dict[key] = {}
        data_dict[key][text[1]] = text[3]

    for key, value in data_dict.items():

        line_dict = {}
        idx, i = -1, 0
        if 0 < key < y_line[-5] and len(value) > 1:
            seg = [0, float("inf")]
        elif y_line[-4] < key < span_y[-1]:
            seg = [0, 35, 55]
        else:
            seg = None

        if seg:
            for k, v in value.items():
                if i < len(seg):
                    if k > seg[i]:
                        i += 1
                        idx = k
                    line_dict[idx] = line_dict.get(idx, "") + v
                else:
                    line_dict[k] = v
            data_dict[key] = line_dict

    return data_dict


def get_custom(ofd_tree):
    """
    获取根目录下OFD.xml文件中的相关元数据
    :param ofd_tree: 解压后的ofd树
    :return: 元数据字典
    """
    custom_dict = {}
    if "ofd:CustomDatas" in ofd_tree["ofd:OFD"]["ofd:DocBody"]["ofd:DocInfo"].keys():
        custom_data = ofd_tree["ofd:OFD"]["ofd:DocBody"]["ofd:DocInfo"][
            "ofd:CustomDatas"
        ]["ofd:CustomData"]
        for data in custom_data:
            custom_dict[data["@Name"]] = data["#text"]

    return custom_dict


def get_other_data(data_dict, x_list, y_list):
    """
    获取除行项目以外的信息
    :param data_dict: 元素字典
    :param y_list: 横向线条y坐标列表
    :return: 除行项目以外的信息
    """

    other_list = [[], [], [], []]

    for key, value in data_dict.items():
        if 0 < key < y_list[-5]:  # 发票信息
            for k, v in value.items():
                other_list[0].append(v)
        elif y_list[-5] <= key < y_list[-4]:  # 购买和销售方信息
            for k, v in value.items():
                if x_list[0] < k < x_list[-1]:
                    other_list[1].append(v)
        elif y_list[-2] <= key < y_list[-1]:  # 备注
            for k, v in value.items():
                if x_list[0] < k < x_list[-1]:
                    other_list[2].append(v)
        elif key > y_list[-1]:  # 开票人信息
            other_list[3].extend(list(value.values()))

    return other_list


def get_table_data(data_dict, span_y, y_line, idx, now_page=None):
    """
    解析行项目信息
    :param data_dict: 元素字典
    :param span_y:
    :param y_line:
    :param idx:
    :param now_page:
    :return:
        table_list: 页面内所有行项目的列表
        last_page: 这一页中属于上一页行项目的内容
    """

    i = 0
    table_line, table_list, last_page = {}, [], []
    for key, value in data_dict.items():
        if (
                y_line[idx] < key < span_y[0]
        ):  # 分页的情况，可能有行项目分为两页，此处存储被分行项目，在处理下一页时进行合并
            last_page.append(value)
        if key == span_y[0]:  # 页面内第一个行项目的第一行初始化，保证循环一致
            table_line = value
        if span_y[i] < key < span_y[i + 1]:  # 行项目中非第一行的元素往第一行进行合并
            for k, v in value.items():
                table_line[k] += v
        elif len(span_y) > 1 and key == span_y[i + 1]:  # 页面内非第一个行项目
            if now_page is not None and key == span_y[-1]:
                for page in now_page:
                    for k, v in page.items():
                        table_line[k] += v
            table_line = list(table_line.values())
            while (
                    len(table_line) < 8
            ):  # 组织好后的行项目若缺失单位或规格型号则需填充空字符
                table_line.insert(1, "")
            table_list.append(table_line)
            table_line = value
            i += 1
        if i == len(span_y) - 1:  # 判断是否处理完所有行项目
            break

    return table_list, last_page


def format_data(other_list, table_list, custom_dict):
    """
    格式化文本方法
    :param other_list: 除行项目以外的信息，可能有发票确实元数据信息，若元数据信息缺失则利用其中部分的值
    :param table_list: 行项目信息
    :param custom_dict: 元数据信息
    :param flag: 元数据获取成功与否的标志
    :return: 格式化后的发票信息列表
    """
    # 格式化文本
    result_list = []
    sum_amount = sum([float(s[5]) for s in table_list])
    sum_tax = sum([float(s[7]) for s in table_list])
    for line in table_list:
        final_dict = {"购买方名称": other_list[1][0], "销售方名称": other_list[1][1]}
        if custom_dict:
            final_dict["发票号码"] = custom_dict["发票号码"]
            final_dict["开票日期"] = custom_dict["开票日期"]
            final_dict["购买方纳税人识别号"] = custom_dict["购买方纳税人识别号"]
            final_dict["销售方纳税人识别号"] = custom_dict["销售方纳税人识别号"]
            final_dict["合计金额"] = float(custom_dict["合计金额"])
            final_dict["合计税额"] = float(custom_dict["合计税额"])
        else:
            final_dict["购买方纳税人识别号"] = other_list[1][2]
            final_dict["销售方纳税人识别号"] = other_list[1][3]
            final_dict["发票号码"] = other_list[0][0]
            final_dict["开票日期"] = other_list[0][1]
            final_dict["合计金额"] = sum_amount
            final_dict["合计税额"] = sum_tax
        final_dict["项目名称"] = line[0]
        final_dict["规格型号"] = line[1]
        final_dict["单位"] = line[2]
        final_dict["数量"] = line[3]
        final_dict["单价"] = float(line[4])
        final_dict["金额"] = float(line[5])
        final_dict["税率/征收率"] = line[6]
        final_dict["税额"] = float(line[7])
        final_dict["备注"] = "\n".join(other_list[2])
        final_dict["开票人"] = other_list[3][0]

        result_list.append(final_dict)

    return result_list


def get_ofd_pase_dict(invoice_data, f_path, f_name, img_path=''):
    # Generate row_data entries from invoice data
    row_data = [
        f"|{item['项目名称']}|{item['规格型号']}|{item['单位']}|{item['数量']}|{item['单价']}|{item['金额']}|{item['税率/征收率']}|{item['税额']}|"
        for item in invoice_data
    ]

    # Extract common information for content section
    content_info = invoice_data[0]
    content = (
        f"购买方名称：{content_info['购买方名称']}\n"
        f"销售方名称：{content_info['销售方名称']}\n"
        f"发票号码：{content_info['发票号码']}\n"
        f"开票日期：{content_info['开票日期']}\n"
        f"购买方纳税人识别号：{content_info['购买方纳税人识别号']}\n"
        f"销售方纳税人识别号：{content_info['销售方纳税人识别号']}\n"
        f"合计金额：{content_info['合计金额']}\n"
        f"合计税额：{content_info['合计税额']}\n"
        f"备注：{content_info['备注']}\n"
        f"开票人：{content_info['开票人']}\n"
        f"<table:table_0>"
    )

    # Create the final dictionary
    res_dic = {
        'file_path': f_path,
        "file_uuid": f_name,
        "image_path": img_path,
        "is_invoice": True,
        "table_info": {
            "table_0": {
                "row_data": row_data,
                "table_head": [
                    "|项目名称|规格型号|单位|数量|单价|金额|税率/征收率|税额|",
                    "|-|-|-|-|-|-|-|-|"
                ]
            }
        },
        "content": content
    }
    return res_dic


def ofd2img(oid, file_path, out_dir):
    with open(file_path, "rb") as f:
        ofdb64 = str(base64.b64encode(f.read()), "utf-8")
    from easyofd import OFD
    ofd = OFD()  # 初始化OFD 工具类
    ofd.read(ofdb64, save_xml=False)  # 读取ofdb64
    imgs = ofd.to_jpg()  # 转图片
    if len(imgs) > 1:
        img = concatenate_images_vertically(imgs)
    else:
        img = imgs[0]
    ofd.del_data()
    name = f'ofd_{oid}.png'
    path = os.path.join(out_dir, name)
    img.save(path)
    return name


def concatenate_images_vertically(images):
    """
    将一组 PIL.Image 图片按纵向拼接。

    参数:
        images (list of PIL.Image): 要拼接的图片列表。

    返回:
        PIL.Image: 拼接后的图像。
    """

    # 计算拼接后的宽度为图片中最大宽度，高度为所有图片高度之和
    total_height = sum(img.height for img in images)
    max_width = max(img.width for img in images)

    # 创建空白画布，大小为拼接后的尺寸
    concatenated_image = Image.new('RGB', (max_width, total_height))

    # 将每张图片逐一粘贴到画布上
    y_offset = 0
    for img in images:
        concatenated_image.paste(img, (0, y_offset))
        y_offset += img.height  # 更新当前纵向偏移量

    return concatenated_image
